package org.tp8.sampleapp.persistence.impl;

import java.io.Serializable;
import java.util.Iterator;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.tp8.sampleapp.model.BaseModel;
import org.tp8.sampleapp.persistence.custom.BaseCustom;
import org.tp8.sampleapp.util.SampleAppLogger;

/**
 * The Class AbstractRepository.
 * 
 * @param <K>
 *            the key type
 * @param <T>
 *            the generic type
 */
@Service
@Transactional(propagation = Propagation.MANDATORY)
public abstract class AbstractRepository<K extends Serializable, T extends BaseModel<K>>
		implements BaseCustom<K, T> {

	/**
	 * The Constant log.
	 */
	protected final static SampleAppLogger log = new SampleAppLogger(
			AbstractRepository.class);

	/**
	 * The entity manager.
	 */
	@PersistenceContext(unitName = "jpaSamplePU")
	@Qualifier(value = "entityManagerFactory")
	protected EntityManager entityManager;

	/**
	 * Gets the entity class.
	 * 
	 * @return the entity class
	 */
	public abstract Class<T> getEntityClass();

	/**
	 * Gets the session.
	 * 
	 * @return the session
	 */
	public Session getSession() {
		return (Session) entityManager.getDelegate();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.tp8.sampleapp.persistence.custom.BaseCustom#queryByExample(org.
	 * springframework.data.domain.Pageable, org.tp8.sampleapp.model.BaseModel)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public Page<T> queryByExample(Pageable pageable, T example) {
		Example curExample = Example.create(example).excludeZeroes();
		// create criteria based on the provided example
		Criteria criteria = getSession().createCriteria(getEntityClass()).add(
				curExample);
		// Count
		long count = (long) criteria.setProjection(Projections.rowCount())
				.uniqueResult();
		// perform the query
		criteria = getSession().createCriteria(getEntityClass())
				.add(curExample);
		criteria.setFirstResult(((pageable.getPageNumber() - 1) * pageable
				.getPageSize()));
		criteria.setMaxResults(pageable.getPageSize());

		Iterator<Sort.Order> it = pageable.getSort().iterator();
		while (it.hasNext()) {
			Sort.Order sortOrder = it.next();
			Order order;
			if (sortOrder.isAscending()) {
				order = Order.asc(sortOrder.getProperty());
			} else {
				order = Order.desc(sortOrder.getProperty());
			}
			criteria.addOrder(order);
		}
		// Create result;
		Page<T> result = new PageImpl<>(criteria.list(), pageable, count);
		return result;
	}
}
